package com.demo.lock;

import java.util.concurrent.CountDownLatch;

/**
 * Created by hunter
 *
 * @Description 一个简单的非自旋锁demo, 与自旋锁的区别是自旋锁一直循环去试图拿到锁, 占用cpu, 而这里会睡眠释放调当前线程锁同时释放当前占有的cpu, 不会再分配时间片过来
 *              但是切换CPU也是需要耗费处理器时间的, 所以锁住的代码块代码不复杂耗时的情况下, 用自旋锁好很多。
 *
 */
public class NonSpinLock {

    // 要被update的数据
    private volatile int data;

    // 要被update的数据 比较值
    private volatile int data2;

    // 等待完成计数器
    private CountDownLatch dataLatch = new CountDownLatch(40);
    private CountDownLatch data2Latch = new CountDownLatch(40);

    // 更新标识
    private volatile boolean updateFlag = false;

    /**
     * 获取锁, 这里跟自旋锁的区别是自旋锁一直循环去试图拿到锁, 占用cpu, 而这里会睡眠释放调当前线程锁同时释放当前占有的cpu, 不会再分配时间片过来
     */
    private void getLock() {
        if (updateFlag) {
            try {
                // synchronized (this) 是因为 this.wait调用必须锁住当前对象,
                synchronized (this) {
                    System.out.println(Thread.currentThread().getName() + " get lock failed ...");
                    // wait会释放调当前线程锁即synchronized, 同时释放cpu, 等待被唤醒
                    this.wait();
                }
                // 被唤醒后再次去尝试获取锁
                getLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            // 这里是线程安全控制, 多个请求进来时, 必须保证只有一个线程修改updateFlag的值
            synchronized (this) {
                // 类似单例模式那种的第二轮判断
                if (! updateFlag) {
                    updateFlag = true;
                } else {
                    // 再次尝试去获取锁
                    getLock();
                }
            }
        }
    }

    /**
     * 解锁, 这里可以判断下持锁的是否是当前线程才让解锁
     */
    private void releaseLock() {
        updateFlag = false;

        // synchronized (this) 是因为 this.notify调用必须锁住当前对象,
        synchronized (this) {
            // 这里随机释放一个线程, 比notifyAll好点, 避免其它线程再次加入并发去抢synchronized锁, 反正都是随机去获取锁
            this.notify();
        }
    }

    /**
     * 加锁更新操作
     */
    public void update() {
        getLock();

        data++;

        releaseLock();
    }

    /**
     * 普通更新操作, 用来做比较, 表明锁起到作用了
     */
    public void normalUpdate() {
        data2++;
    }

    public static void main(String[] args) throws InterruptedException {
        NonSpinLock lockTest = new NonSpinLock();

        for (int i = 0; i < 40; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lockTest.update();
                }
                lockTest.dataLatch.countDown();
            }).start();
        }

        // 这里拿普通的++操作来对比, 可以看出是线程不安全的
        for (int i = 0; i < 40; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lockTest.normalUpdate();
                }
                lockTest.data2Latch.countDown();
            }).start();
        }

        // 等待结束
        lockTest.dataLatch.await();
        lockTest.data2Latch.await();

        // 打印值检测线程安全结果
        System.out.println(lockTest.data);
        System.out.println(lockTest.data2);
    }
}
